import { Parser, Token, TokenType } from './parser';

const fs = require('fs');

export class Commands {
	private constructor() {
		this.commands = new Map<string, GenerationCommand>();
		fs.readdirSync('./src/generators').filter((it: string) => it.endsWith('.js')).forEach((it: string) => {
			let path = './generators/' + it;
			delete require.cache[require.resolve(path)];
			let stuff = require(path);
			Object.keys(stuff).forEach(key => {
				let cls = stuff[key];
				let instance = new cls();
				if (instance instanceof GenerationCommand) {
					this.commands.set(instance.name, instance);
				}
			});
		});
	}

	public static getCommands() {
		if (!this.instance) {
			this.instance = new Commands();
		}
		return this.instance.commands;
	}

	private commands: Map<string, GenerationCommand>;
	private static instance: Commands;
}

export abstract class GenerationCommand {
	abstract name: string;
	abstract generate: (token: Token, params?: Map<string, string>) => string;
}

export abstract class SimpleGenerationCommand extends GenerationCommand {
	generate = (token: Token) => {
		let parser = new Parser(token.content);
		let gen = new Generator(parser, false);
		return this.wrap(gen.generateHTML());
	};

	abstract wrap: (str: string, params?: Map<string, string>) => string;
}

export class Generator {
	constructor(parser: Parser, wrapNakedText = true) {
		this.parser = parser;
		this.wrapNakedText = wrapNakedText;
	}

	public generateHTML(): string {
		let result = '';
		let token;
		do {
			token = this.parser.emit();
			if (token.type == TokenType.COMMAND) {
				result += this.runCommand(token);
			} else if (token.type == TokenType.BLOCK_BREAK) {
				result += `<br>`;
			} else {
				if (this.wrapNakedText) {
					result += `<p>${token.content}</p>`;
				} else {
					result += `${token.content}`;
				}
			}
		} while (token.type != TokenType.EOF);
		return result;
	}

	private runCommand(token: Token): string {
		if (token.type == TokenType.EOF) {
			return '';
		}
		let result = '';
		let commands = Commands.getCommands();
		let cmd = commands.get(token.content);
		if (cmd) {
			let nextToken = this.parser.emit();
			if (nextToken.type == TokenType.COMMAND) {
				result += cmd.generate(new Token(TokenType.TEXT, ''));
				result += this.runCommand(nextToken);
			} else if (nextToken.type == TokenType.COMMAND_PARAMETERS) {
				let tokenAfterTheNext = this.parser.emit();
				let parameterGlob = nextToken.content;
				if (parameterGlob.trim().length < 1) {
					result += cmd.generate(tokenAfterTheNext);
				} else {
					let map = new Map<string, string>();
					let items = parameterGlob.split(',');
					let counter = 0;
					items.forEach(item => {
						let parts = item.split('=', 1);
						// assert parts.length > 0
						if (parts.length == 1) {
							map.set(counter.toString(), parts[0]);
						} else {
							map.set(parts[0], parts[1]);
						}
					});
					result += cmd.generate(tokenAfterTheNext, map);
				}
			} else {
				result += cmd.generate(nextToken);
			}
		} else {
			console.log(`Undefined control sequence: ${token.content}`);
			return result;
		}
		return result;
	}

	private readonly parser: Parser;
	private readonly wrapNakedText: boolean;
}
